package cn.tedu.lbs.article.web.repository;

import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.tarena.lbs.pojo.content.entity.ActionSearchEntity;
import com.tarena.lbs.pojo.content.entity.ArticleSearchEntity;
import com.tarena.lbs.pojo.content.query.ArticleQuery;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Repository
@Slf4j
public class ArticleRepository {
    @Autowired
    private RestHighLevelClient client;
    //利用query条件携带参数非空数据 组织查询query到es中进行搜索
    //执行 es的搜索查询功能 因为业务中使用到了lat lon导致es的repository不适用
    public List<ArticleSearchEntity> searchArticles(ArticleQuery articleQuery) {
        //1.利用非空属性组织一个bool查询条件
        BoolQueryBuilder boolQuery=assembleBoolQuery(articleQuery);
        //2.执行搜索 不考虑分页
        try{
            SearchRequest request=new SearchRequest("lbs_article");
            SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
            sourceBuilder.query(boolQuery);
            //打印一下具备查询条件的请求参数对象
            log.info("搜索文章,请求参数builder:{}",sourceBuilder);
            request.source(sourceBuilder);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(response);
        }catch (Exception e){
            //打印日志 返回空
            log.error("搜索文章,入参:{},失败",articleQuery,e);
            return null;
        }
    }

    private List<ArticleSearchEntity> parseResponse(SearchResponse response) {
        //不关心total maxScore只关心 hits命中文档集合中source
        SearchHit[] docs = response.getHits().getHits();
        //如果集合是空 说明查询条件没有命中任何文档
        List<ArticleSearchEntity> entities=null;
        //hits非空 转化entities
        if (docs!=null&&docs.length>0){
            entities=Arrays.stream(docs).map(doc->{
                //将source读取json 转化成entity 使用fastJson工具
                return JSON.parseObject(doc.getSourceAsString(),ArticleSearchEntity.class);
            }).collect(Collectors.toList());
        }
        return entities;
    }

    private BoolQueryBuilder assembleBoolQuery(ArticleQuery articleQuery) {
        //应该在这个方法中 准备bool查询条件 根据非空拼接must逻辑的子条件
        BoolQueryBuilder boolQuery=new BoolQueryBuilder();
        //1.文章标题 matchQuery
        if (articleQuery.getArticleTitle()!=null){
            boolQuery.must(QueryBuilders.matchQuery("articleTitle",articleQuery.getArticleTitle()));
        }
        //2.后台查询 userId非空 查询某个作者的文章
        if (articleQuery.getUserId()!=null){
            boolQuery.must(QueryBuilders.termQuery("userId",articleQuery.getUserId()));
        }
        //3.后台查询 从status数组中拿到第一个元素 查询这个状态的文章
        if (CollectionUtils.isNotEmpty(articleQuery.getArticleStatus())){
            Integer status = articleQuery.getArticleStatus().get(0);
            boolQuery.must(QueryBuilders.termQuery("articleStatus",status));
        }
        //4.后台查询 source=2 注意 前台小程序的查询条件 不携带这个数据 1和2都查
        if (articleQuery.getSource()!=null){
            boolQuery.must(QueryBuilders.termQuery("source",articleQuery.getSource()));
        }
        //地理位置的搜索拼接
        if (articleQuery.getLatitude()!=null&&articleQuery.getLongitude()!=null){
            //搜索默认 100公里范围内数据
            GeoDistanceQueryBuilder query = QueryBuilders.geoDistanceQuery("location");
            //提供中心点
            query.point(
                    Double.valueOf(articleQuery.getLatitude()),
                    Double.valueOf(articleQuery.getLongitude()));
            query.distance(100l, DistanceUnit.KILOMETERS);
            boolQuery.must(query);
        }
        //前台属性 补充标签非空条件
        if (articleQuery.getArticleLabel()!=null){
            boolQuery.must(QueryBuilders.termQuery("articleLabel",articleQuery.getArticleLabel()));
        }
        return boolQuery;
    }

    public List<ArticleSearchEntity> searchLabels(ArticleQuery articleQuery) {
        SearchRequest request=new SearchRequest("lbs_article");
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //1.拼query bool查询 拼接非空属性 lat lon articleStatus 复用assembleBoolQuery
        articleQuery.setArticleStatus(Arrays.asList(1));
        articleQuery.setSource(null);
        BoolQueryBuilder boolQuery=assembleBoolQuery(articleQuery);
        //2.拼articleLabel字段去重
        sourceBuilder.collapse(new CollapseBuilder("articleLabel"));
        //对当前业务封装的sourceBuilder打桩 查看bool条件是否正确
        log.info("范围内容标签集合查询query:{}",sourceBuilder);
        request.source(sourceBuilder);
        try {
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return parseResponse(response);
        } catch (IOException e) {
            log.error("搜索范围文章标签集合失败",e);
            return null;
        }
    }

    public List<ActionSearchEntity> selectActionArticles(Integer userId, Integer behave) {
        SearchRequest request=new SearchRequest("lbs_action");
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        sourceBuilder.collapse(new CollapseBuilder("articleId"));
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        query.must(QueryBuilders.termQuery("behaviorUserId",userId));
        query.must(QueryBuilders.termQuery("behavior",behave));
        sourceBuilder.query(query);
        request.source(sourceBuilder);
        List<ActionSearchEntity> entities=null;
        try{
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //关心命中的数据
            SearchHit[] docs = response.getHits().getHits();
            if (docs!=null&&docs.length>0){
                entities=Arrays.stream(docs).map(doc->{
                    return JSON.parseObject(doc.getSourceAsString(),ActionSearchEntity.class);
                }).collect(Collectors.toList());
            }
        }catch (Exception e){
            log.error("查询用户点赞收藏文章失败",e);
        }
        return entities;
    }
}
